﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TeamRobot;
using TeamRobot.Swtor;

namespace ItemDataGenerator
{
    /// <summary>
    /// Generates the item-sources.json file.
    /// </summary>
    public partial class ItemSourceWriter
    {
        private const string ITEM_PATH = @"..\..\items.json";
        private const string MANUAL_PATH = @"..\..\manual-sources.csv";
        private const string OUTPUT_PATH = @"..\..\item-sources.json";

        public ItemSourceWriter()
        {

        }

        public void Write()
        {
            // load up all of the item data
            List<Item> items = new List<Item>();
            string contents = File.ReadAllText(ITEM_PATH, Encoding.UTF8);
            items = JsonUtility.DeserializeObject<List<Item>>(contents);

            Dictionary<int, List<ItemSource>> finalSources = new Dictionary<int, List<ItemSource>>();

            // first grab manual sources entered in manual-sources.csv
            ParseManualSources(finalSources);

            // there are a lot of set items and they are rather formulaic, so use some code to fill in those sources
            GenerateSetItemSources(items, finalSources);

            // a bunch of the epic mods from operations
            GenerateEpicModSources(items, finalSources);

            // handles the epic relics found in operations
            GenerateEpicPveRelicSources(items, finalSources);

            // all done, write to a file (format it to be readable with indents and stuff)
            string json = JsonUtility.SerializeObject(finalSources, true);
            File.WriteAllText(OUTPUT_PATH, json, Encoding.UTF8);
        }

        /// <summary>
        /// Parse the manual-sources.csv file.  Very basic, the columns correspond exactly to properties on the ItemSource object.
        /// </summary>
        /// <param name="sources"></param>
        private void ParseManualSources(Dictionary<int, List<ItemSource>> sources)
        {
            foreach (string line in File.ReadAllLines(MANUAL_PATH, Encoding.UTF8).Skip(1))
            {
                int itemId = -1;
                ItemSource source = new ItemSource();

                int tokenPos = 0;
                string token = "";
                bool inQuotes = false;
                for (int i = 0; i < line.Length; i++)
                {
                    if (i == line.Length - 1 && line[i] != ',' && !(inQuotes && line[i] == '"'))
                    {
                        token += line[i];
                    }

                    if ((!inQuotes && line[i] == ',') || i == line.Length - 1)
                    {
                        // we have reached the end of a token, so read it
                        if (!string.IsNullOrEmpty(token))
                        {
                            switch (tokenPos)
                            {
                                case 0:
                                    itemId = int.Parse(token);
                                    break;
                                case 1:
                                    // don't care about the name, it's just for readability
                                    break;
                                case 2:
                                    source.SourceType = Utility.Enum.Parse<SourceType>(token);
                                    break;
                                case 3:
                                    source.Zone = token;
                                    break;
                                case 4:
                                    source.Difficulty = Utility.Enum.Parse<ZoneDifficulty>(token);
                                    break;
                                case 5:
                                    source.Npc = token;
                                    break;
                                case 6:
                                    source.Currency = token;
                                    break;
                                case 7:
                                    source.Cost = token;
                                    break;
                                case 8:
                                    source.Faction = Utility.Enum.Parse<Faction>(token);
                                    break;
                            }
                        }

                        tokenPos++;
                        if (tokenPos > 8) break;

                        token = "";
                    }
                    else if (!inQuotes && line[i] == '"')
                    {
                        inQuotes = true;
                    }
                    else if (inQuotes && line[i] == '"')
                    {
                        if (line[i + 1] != '"')
                            inQuotes = false;
                        else
                        {
                            // double-quote escapes a quote
                            token += '"';
                            i++;
                        }
                    }
                    else
                        token += line[i];
                }

                if (itemId != -1)
                {
                    List<ItemSource> sourceList;
                    if (!sources.TryGetValue(itemId, out sourceList))
                        sourceList = sources[itemId] = new List<ItemSource>();
                    sourceList.Add(source);
                }
            }
        }

        private void GenerateEpicModSources(List<Item> items, Dictionary<int, List<ItemSource>> sources)
        {
            HashSet<int> doneItems = new HashSet<int>();

            // items with well-recognized names that we can match to costs
            foreach (Item item in items.Where(i => i.ModType.HasValue && i.Quality == Quality.Artifact && i.ItemLevel == 58 && !i.Name.StartsWith("Mastercraft")))
            {
                if (doneItems.Contains(item.Id)) continue;
                doneItems.Add(item.Id);

                // skip mods that have a slot restriction -- they can only be ripped
                if (item.ModSlotRestriction.HasValue) continue;

                if (item.IsPvpItem == true) continue;

                switch (item.ModType.Value)
                {
                    case ModTypeEnum.Mod:
                        AddSource(item, sources, new ItemSource
                        {
                            SourceType = SourceType.Operation,
                            Zone = "The Eternity Vault",
                            Npc = "Gharj",
                            Difficulty = ZoneDifficulty.Hard,
                        });
                        AddSource(item, sources, new ItemSource
                        {
                            SourceType = SourceType.Operation,
                            Zone = "The Eternity Vault",
                            Npc = "Infernal Council",
                            Difficulty = ZoneDifficulty.Hard,
                        });
                        AddSource(item, sources, new ItemSource
                        {
                            SourceType = SourceType.Operation,
                            Zone = "Karagga's Palace",
                            Npc = "Jarg and Sorno",
                            Difficulty = ZoneDifficulty.Hard,
                        });
                        break;
                    case ModTypeEnum.Enhancement:
                        AddSource(item, sources, new ItemSource
                        {
                            SourceType = SourceType.Operation,
                            Zone = "The Eternity Vault",
                            Npc = "Gharj",
                            Difficulty = ZoneDifficulty.Hard,
                        });
                        AddSource(item, sources, new ItemSource
                        {
                            SourceType = SourceType.Operation,
                            Zone = "The Eternity Vault",
                            Npc = "Infernal Council",
                            Difficulty = ZoneDifficulty.Hard,
                        });
                        AddSource(item, sources, new ItemSource
                        {
                            SourceType = SourceType.Operation,
                            Zone = "Karagga's Palace",
                            Npc = "Jarg and Sorno",
                            Difficulty = ZoneDifficulty.Hard,
                        });
                        AddSource(item, sources, new ItemSource
                        {
                            SourceType = SourceType.Operation,
                            Zone = "Karagga's Palace",
                            Npc = "G4-B3 Heavy Fabricator",
                            Difficulty = ZoneDifficulty.Hard,
                        });
                        break;
                    case ModTypeEnum.Armoring:
                        AddSource(item, sources, new ItemSource
                        {
                            SourceType = SourceType.Operation,
                            Zone = "The Eternity Vault",
                            Npc = "Soa",
                            Difficulty = ZoneDifficulty.Hard,
                        });
                        AddSource(item, sources, new ItemSource
                        {
                            SourceType = SourceType.Operation,
                            Zone = "Karagga's Palace",
                            Npc = "Karagga the Unyielding",
                            Difficulty = ZoneDifficulty.Hard,
                        });
                        break;
                }
            }
        }

        private void GenerateEpicPveRelicSources(List<Item> items, Dictionary<int, List<ItemSource>> sources)
        {
            HashSet<int> doneItems = new HashSet<int>();

            foreach (Item item in items.Where(i => i.Slot == ItemSlot.Relic && i.Quality == Quality.Artifact && i.ItemLevel >= 50 && i.IsPvpItem != true))
            {
                if (doneItems.Contains(item.Id)) continue;
                doneItems.Add(item.Id);

                if (item.OnUse == true)
                {
                    switch (item.ItemLevel)
                    {
                        case 58:
                            AddSource(item, sources, new ItemSource
                            {
                                SourceType = SourceType.Operation,
                                Zone = "The Eternity Vault",
                                Npc = "Infernal Council",
                                Difficulty = ZoneDifficulty.Hard,
                            });
                            break;
                        case 56:
                            AddSource(item, sources, new ItemSource
                            {
                                SourceType = SourceType.Operation,
                                Zone = "The Eternity Vault",
                                Npc = "Infernal Council",
                            });
                            break;
                    }
                }
                else
                {
                    switch (item.ItemLevel)
                    {
                        case 58:
                            AddSource(item, sources, new ItemSource
                            {
                                SourceType = SourceType.Operation,
                                Zone = "Karagga's Palace",
                                Npc = "G4-B3 Heavy Fabricator",
                                Difficulty = ZoneDifficulty.Hard,
                            });
                            break;
                        case 56:
                            AddSource(item, sources, new ItemSource
                            {
                                SourceType = SourceType.Operation,
                                Zone = "Karagga's Palace",
                                Npc = "G4-B3 Heavy Fabricator",
                            });
                            break;
                    }
                }

            }
        }

        /// <summary>
        /// The Slot field is a bit more specific than is generally required for item source code, this returns a more generic slot.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        private static ItemSlot GetBetterSlot(Item item)
        {
            ItemSlot slot = item.Slot.Value;
            switch (item.Slot.Value)
            {
                case ItemSlot.PrimaryRanged:
                case ItemSlot.MainHand:
                    slot = ItemSlot.MainHand;
                    break;
                case ItemSlot.SecondaryRanged:
                case ItemSlot.EitherRanged:
                case ItemSlot.CustomRanged:
                case ItemSlot.Shield:
                case ItemSlot.OffHand:
                case ItemSlot.EitherHand:
                case ItemSlot.CustomMelee:
                    slot = ItemSlot.OffHand;
                    break;
            }
            return slot;
        }

        /// <summary>
        /// Helper to add a source, makes code a little cleaner.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="sources"></param>
        /// <param name="newSource"></param>
        private void AddSource(Item item, Dictionary<int, List<ItemSource>> sources, ItemSource newSource)
        {
            List<ItemSource> list;
            if (!sources.TryGetValue(item.Id, out list))
                list = sources[item.Id] = new List<ItemSource>();
            list.Add(newSource);
        }

        #region testing code

        /// <summary>
        /// These are used by the Test method to do testing of specific methods.
        /// </summary>
        public enum SourceGeneratorTypes
        {
            Manual,
            EpicMod,
            EpicPveRelic,
            Centurion,
            Champion,
            Battlemaster,
            Tionese,
            Columi,
            Rakata,
            Energized,
            Xenotech,
            Exotech,
            BlackHole,
            BlackHoleMk3,
            WarHero,
            WarHeroRated,
            Recruit,
            Campaign,
            SpecialSetSources,
        }

        /// <summary>
        /// Test the specified part of item source generation.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="reportPath">If specified, save a report of the results to the specified file; otherwise prints to the console.</param>
        public void Test(SourceGeneratorTypes type, string reportPath = null)
        {
            // load up all of the item data
            List<Item> items = new List<Item>();
            string contents = File.ReadAllText(ITEM_PATH, Encoding.UTF8);
            items = JsonUtility.DeserializeObject<List<Item>>(contents);

            Dictionary<int, List<ItemSource>> finalSources = new Dictionary<int, List<ItemSource>>();
            var sources = finalSources;

            switch (type)
            {
                case SourceGeneratorTypes.Manual:
                    ParseManualSources(finalSources);
                    break;
                case SourceGeneratorTypes.EpicMod:
                    GenerateEpicModSources(items, finalSources);
                    break;
                case SourceGeneratorTypes.EpicPveRelic:
                    GenerateEpicPveRelicSources(items, finalSources);
                    break;
                case SourceGeneratorTypes.SpecialSetSources:
                    AddSpecialSetSourcesByName(items, finalSources);
                    break;
                default:

                    // these are the various set item methods

                    HashSet<int> doneItems = new HashSet<int>();

                    // items with well-recognized names that we can match to costs
                    foreach (Item item in items.Where(i => IsSetItem(i)))
                    {
                        if (doneItems.Contains(item.Id)) continue;
                        doneItems.Add(item.Id);

                        string[] words = item.Name.Split(' ');

                        // this block makes this method only do the one specified type, instead of all of them
                        switch (words[0])
                        {
                            case "Black":
                                if (item.Name.Contains("MK-1") && type != SourceGeneratorTypes.BlackHole)
                                    continue;
                                else if ((item.Name.Contains("MK-2") || item.Name.Contains("MK-3")) && type != SourceGeneratorTypes.BlackHoleMk3)
                                    continue;
                                break;
                            case "War":
                                if (words[words.Length - 1] == "(Rated)" && type != SourceGeneratorTypes.WarHeroRated)
                                    continue;
                                else if (type != SourceGeneratorTypes.WarHero)
                                    continue;
                                break;
                            default:
                                if (words[0] != type.ToString()) continue;
                                break;
                        }

                        switch (words[0])
                        {
                            case "Centurion":
                                AddCenturionSource(item, sources);
                                break;
                            case "Champion":
                                AddChampionSource(item, sources);
                                break;
                            case "Battlemaster":
                                AddBattlemasterSource(item, sources);
                                break;
                            case "Tionese":
                                AddTioneseSource(item, sources);
                                break;
                            case "Columi":
                                AddColumiSource(item, sources);
                                break;
                            case "Rakata":
                                AddRakataSource(item, sources);
                                break;
                            case "Energized":
                                AddEnergizedSource(item, sources);
                                break;
                            case "Xenotech":
                                AddXenotechSource(item, sources);
                                break;
                            case "Exotech":
                                AddExotechSource(item, sources);
                                break;
                            case "Black":
                                if (item.Name.Contains("MK-1"))
                                    AddBlackHoleSource(item, sources);
                                else if (item.Name.Contains("MK-2") || item.Name.Contains("MK-3"))
                                    AddBlackHoleMk3Source(item, sources);
                                break;
                            case "War":
                                if (words[words.Length - 1] == "(Rated)")
                                    AddWarHeroRatedSource(item, sources);
                                else
                                    AddWarHeroSource(item, sources);
                                break;
                            case "Recruit":
                                AddRecruitSource(item, sources);
                                break;
                            case "Campaign":
                                AddCampaignSource(item, sources);
                                break;
                        }
                    }

                    break;
            }

            // hash items to id
            int longestId = 0;
            int longestName = 0;
            Dictionary<int, Item> itemsById = new Dictionary<int, Item>();
            foreach (Item item in items)
            {
                itemsById[item.Id] = item;

                longestId = Math.Max(longestId, item.Id.ToString().Length);
                longestName = Math.Max(longestName, item.Name.Length);
            }

            // generate a report of the sources generated

            StringBuilder sb = new StringBuilder();
            foreach (var entry in finalSources.OrderBy(x => x.Key))
            {
                Item item = itemsById[entry.Key];
                foreach (ItemSource source in entry.Value.OrderBy(x => x.ToString()))
                {
                    string line = item.Id.ToString().PadRight(longestId) + "  " + item.Name.PadRight(longestName) + "  " + source.ToString();
                    if (!string.IsNullOrEmpty(reportPath))
                        sb.AppendLine(line);
                    else
                        Console.WriteLine(line);
                }
            }

            if (!string.IsNullOrEmpty(reportPath))
                File.WriteAllText(reportPath, sb.ToString(), Encoding.UTF8);
        }

        #endregion
    }
}
